<template>
  <el-form v-bind="getFormProp(formData)" ref="xmform">
    <el-row v-bind="getRowProps()">
      <el-col
        v-for="(value, name) in formData.model"
        :key="name"
        v-bind="getColProps(name)"
        v-show="!formData.props[name].hidden"
      >
        <el-form-item :label="formData.props[name].label" :prop="name">
          <!-- input类型 -->
          <el-input
            v-if="formData.props[name].type === 'text'"
            v-model="formData.model[name]"
            v-bind="formData.props[name]"
            v-on="formData.methods[name]"
            :maxlength="
              formData.props[name].maxlength
                ? formData.props[name].maxlength
                : '128'
            "
          >
            <template
              v-for="slotName in formData.props[name].slotName"
              :slot="slotName"
            >
              <div :key="slotName">
                <slot :name="name + '-' + slotName"></slot>
              </div>
            </template>
          </el-input>
          <!-- number类型 -->
          <el-input
            v-if="formData.props[name].type === 'number'"
            v-model="formData.model[name]"
            v-bind="getItemProps(name)"
            v-on="formData.methods[name]"
            @input="
              value =>
                mergeInputNumberFun(name, value, formData.props[name].precision)
            "
            @change="value => mergeChangeNumberFun(name, value)"
          >
            <template
              v-for="slotName in formData.props[name].slotName"
              :slot="slotName"
            >
              <div :key="slotName">
                <slot :name="name + '-' + slotName"></slot>
              </div>
            </template>
          </el-input>

          <!-- inputNumber类型 -->
          <el-input-number
            v-if="formData.props[name].type === 'input-number'"
            v-model="formData.model[name]"
            v-bind="formData.props[name]"
            v-on="formData.methods[name]"
          >
          </el-input-number>
          <!-- textarea类型 -->
          <el-input
            v-if="formData.props[name].type === 'textarea'"
            v-model="formData.model[name]"
            v-bind="formData.props[name]"
            v-on="formData.methods[name]"
          ></el-input>
          <!-- date-picker -->
          <el-date-picker
            v-if="isDateType(formData.props[name].type)"
            v-model="formData.model[name]"
            v-bind="formData.props[name]"
            v-on="formData.methods[name]"
            :picker-options="formData.props[name].pickerOptions"
            class="w-100"
          >
          </el-date-picker>
          <!-- time-picker -->
          <el-time-picker
            v-if="formData.props[name].type === 'time'"
            v-model="formData.model[name]"
            v-bind="formData.props[name]"
            v-on="formData.methods[name]"
            class="w-100"
          >
          </el-time-picker>
          <!-- select类型 -->
          <el-select
            v-if="formData.props[name].type === 'select'"
            v-model="formData.model[name]"
            v-bind="formData.props[name]"
            v-on="formData.methods[name]"
            class="w-100"
          >
            <el-option
              v-for="option in formData.props[name].options"
              :key="option.value"
              :label="option.label"
              :value="option.value"
              :disabled="option.disabled"
            ></el-option>
          </el-select>
          <!-- treeselect -->
          <treeselect
            :class="classSize"
            v-if="formData.props[name].type === 'tree-select'"
            v-model="formData.model[name]"
            v-bind="formData.props[name]"
            v-on="formData.methods[name]"
          >
            <template
              :slot="slotName"
              slot-scope="{
                node,
                shouldShowCount,
                count,
                labelClassName,
                countClassName
              }"
              v-for="slotName in formData.props[name].slotName"
            >
              <div :key="slotName">
                <template v-if="slotName == 'option-label'">
                  <slot
                    :name="name + '-' + slotName"
                    :node="node"
                    :shouldShowCount="shouldShowCount"
                    :count="count"
                    :labelClassName="labelClassName"
                    :countClassName="countClassName"
                  ></slot>
                </template>
                <template v-if="slotName == 'value-label'">
                  <slot :name="name + '-' + slotName" :node="node"></slot>
                </template>

                <template
                  v-if="slotName == 'before-list' || slotName == 'after-list'"
                >
                  <slot :name="name + '-' + slotName"></slot>
                </template>
              </div>
            </template>
          </treeselect>
          <!-- switch -->
          <el-switch
            v-if="formData.props[name].type === 'switch'"
            v-model="formData.model[name]"
            v-bind="formData.props[name]"
            v-on="formData.methods[name]"
          ></el-switch>
          <!-- radio -->
          <el-radio-group
            v-if="formData.props[name].type === 'radio'"
            v-model="formData.model[name]"
            v-bind="formData.props[name]"
            v-on="formData.methods[name]"
          >
            <el-radio
              v-for="option in formData.props[name].options"
              :key="option.value"
              :label="option.value"
              >{{ option.label }}</el-radio
            >
          </el-radio-group>
          <!-- radio-button -->
          <el-radio-group
            v-if="formData.props[name].type === 'radio-button'"
            v-model="formData.model[name]"
            v-bind="formData.props[name]"
            v-on="formData.methods[name]"
          >
            <el-radio-button
              v-for="option in formData.props[name].options"
              :key="option.value"
              :label="option.value"
              >{{ option.label }}</el-radio-button
            >
          </el-radio-group>
          <!-- checkbox -->
          <el-checkbox-group
            v-if="formData.props[name].type === 'checkbox'"
            v-model="formData.model[name]"
            v-bind="formData.props[name]"
            v-on="formData.methods[name]"
          >
            <el-checkbox
              v-for="option in formData.props[name].options"
              :key="option.value"
              :label="option.value"
              :disabled="option.disabled || false"
              >{{ option.label }}</el-checkbox
            >
          </el-checkbox-group>
          <!-- cascader -->
          <el-cascader
            v-if="formData.props[name].type === 'cascader'"
            v-model="formData.model[name]"
            v-bind="formData.props[name]"
            v-on="formData.methods[name]"
            class="w-100"
          ></el-cascader>
          <!-- file类型 -->
          <el-upload
            v-if="formData.props[name].type === 'file'"
            :ref="name"
            :on-error="handleFileError"
            v-bind="formData.props[name]"
            :on-progress="mergeProgress"
          >
            <i
              v-if="
                formData.props[name].listType &&
                  formData.props[name].listType === 'picture-card'
              "
              class="el-icon-plus"
            ></i>
            <div v-else style="text-align: left">
              <div v-if="formData.props[name].trigger">
                <el-button slot="trigger" type="primary">选取文件</el-button>
                <el-button
                  style="margin-left: 10px"
                  type="success"
                  @click.stop="submitUpload(name, formData.props[name])"
                  >上传到服务器</el-button
                >
              </div>
              <el-button v-else type="primary">点击上传</el-button>
            </div>
            <div slot="tip" class="el-upload__tip">
              <div v-html="formData.props[name].tip"></div>
              <slot :name="name + '-tag'"></slot>
            </div>

            <!-- <template
              v-for="slotName in formData.props[name].slotName"
              :slot="slotName"
            >
              <div
                v-if="slotName === 'tip'"
                :key="slotName"
                class="el-upload__tip"
              >
                <slot :name="name + '-' + slotName"></slot>
              </div> -->

            <!-- </template> -->
          </el-upload>
          <!-- 富文本编辑器 -->
          <div v-if="formData.props[name].type === 'richeditor'">
            <quillEditor
              v-model="formData.model[name]"
              v-bind="formData.props[name]"
              v-on="formData.methods[name]"
              :options="editorOption"
              class="w-100"
            />
          </div>

          <!-- 自定义form拓展 -->
          <slot
            v-if="formData.props[name].type === 'custom'"
            :name="name"
          ></slot>
        </el-form-item>
      </el-col>
    </el-row>
    <!-- 底部按钮 -->
    <slot name="footer" :form="formData.model"></slot>
  </el-form>
</template>

<script>
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
import { quillEditor } from "vue-quill-editor";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
  name: "el-form-pro",
  components: {
    quillEditor,
    Treeselect
  },
  props: {
    formProps: {
      type: Object,
      default: () => ({})
    },
    rowProp: {
      type: Object,
      default: () => ({
        gutter: 15
      })
    },
    colProp: {
      type: Object,
      default: () => ({
        span: 24
      })
    }
  },
  watch: {
    formProps: {
      handler(val, oldVal) {
        //这里是因为assign只能做到浅拷贝
        const isEmptyModel = !Object.keys(this.formData.model).length;
        if (isEmptyModel) {
          this.formData = Object.assign({}, this.formData, val);
        } else {
          // 合并custom插槽表单与组件内表单的model
          const customObj = {};
          Object.entries(this.formData.props).forEach(([key, value]) => {
            if (value.type === "custom" || value.type === "file") {
              customObj[key] = val.model[key];
            }
          });
          this.formData.model = Object.assign(
            {},
            this.formData.model,
            customObj
          );
        }
        //拷贝一份外面传进来的方法
        for (const key in this.formData.props) {
          if (
            this.formData.props[key].type === "file" &&
            this.formData.props[key]["on-progress"]
          ) {
            this.progressFun = this.formData.props[key]["on-progress"];
            this.$delete(this.formData.props[key], "on-progress");
          }
          if (this.formData.methods[key]) {
            if (
              this.formData.props[key].type === "number" &&
              this.formData.methods[key].change
            ) {
              this.changeNumberFun = this.formData.methods[key].change;
              this.$delete(this.formData.methods[key], "change");
            }
            if (
              this.formData.props[key].type === "number" &&
              this.formData.methods[key].input
            ) {
              this.inputNumberFun = this.formData.methods[key].input;
              this.$delete(this.formData.methods[key], "input");
            }
          }
        }
      },
      deep: true,
      immediate: true
    }
  },
  computed: {
    classSize() {
      if (this.size == "medium") {
        return "treeselectMainMedium";
      } else if (this.size == "mini") {
        return "treeselectMainMini";
      } else {
        return "treeselectMainSmall";
      }
    }
  },
  beforeDestroy() {
    this.resetForm();
  },
  data() {
    return {
      formData: {
        model: {},
        rules: {},
        props: {},
        methods: {}
      },

      dateMap: [
        "year",
        "month",
        "date",
        "dates",
        "week",
        "datetime",
        "datetimerange",
        "daterange",
        "monthrange"
      ],
      editorOption: {
        modules: {
          toolbar: [
            ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
            ["blockquote", "code-block"], // 引用  代码块
            [{ header: 1 }, { header: 2 }], // 1、2 级标题
            [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
            // [{ script: "sub" }, { script: "super" }], // 上标/下标
            [{ indent: "-1" }, { indent: "+1" }], // 缩进
            // [{'direction': 'rtl'}],                         // 文本方向
            [{ size: ["small", false, "large", "huge"] }], // 字体大小
            [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
            [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
            [{ font: [] }], // 字体种类
            [{ align: [] }], // 对齐方式
            ["clean"], // 清除文本格式
            ["link", "image", "video"] // 链接、图片、视频
          ] //工具菜单栏配置
        },
        placeholder: "请输入内容"
      },

      size: "small",
      //存放外面传进来的on-progress的函数
      progressFun: undefined,
      changeNumberFun: undefined,
      inputNumberFun: undefined
    };
  },
  methods: {
    // 合并事件input
    mergeInputNumberFun(name, value, precision) {
      this.formatNumber(name, value, precision);
      if (typeof this.inputNumberFun === "function") {
        this.inputNumberFun(value);
      }
    },
    //合并事件change
    mergeChangeNumberFun(name, value) {
      this.handleNumberChange(name, value);
      if (typeof this.changeNumberFun === "function") {
        this.changeNumberFun(value);
      }
    },
    // 处理row
    getRowProps() {
      return Object.assign({}, this.rowProp);
    },
    //处理col。注：（formData.props权重大于colProp，前提条件是它俩要有同样的属性名）
    getColProps(name) {
      const newProp = Object.assign({}, this.colProp);
      //获取col标签属性（formData.props权重大于colProp）
      const selfProp = this.formData.props[name];
      const formProps = Object.keys(selfProp);
      Object.keys(newProp).forEach(key => {
        if (formProps.includes(key)) {
          newProp[key] = selfProp[key];
        }
      });
      return newProp;
    },
    //获取formProps所需要的model
    getFormProp(formData) {
      const newProp = Object.assign({}, { labelWidth: "auto" }, formData);
      Reflect.deleteProperty(newProp, "props");
      Reflect.deleteProperty(newProp, "methods");
      if (newProp.size) {
        this.size = newProp.size;
      } else {
        this.size = "small";
      }
      return newProp;
    },
    getItemProps(name) {
      const itemProp = Object.assign({}, this.formData.props[name]);
      if (itemProp.type === "number") {
        itemProp.type = "text";
      }
      if (itemProp.type === "int") {
        itemProp.type = "number";
      }
      return itemProp;
    },
    formatNumber(name, value, precision = 0) {
      const isMinus = value.charAt(0) === "-";
      const numberValue = value
        .replace(/[^\d.]/g, "")
        .replace(new RegExp(`\\.{${precision},}`, "g"), ".")
        .replace(".", "$#$")
        .replace(/\./g, "")
        .replace("$#$", ".")
        .replace(new RegExp(`^(-)*(\\d+)\\.(\\d{${precision},}).*$`), "$1$2.$3")
        .replace(/^\./g, "");
      const minusMap = { true: "-", false: "" };
      const floatValue = parseFloat(minusMap[isMinus] + numberValue);
      if (isNaN(floatValue)) {
        this.formData.model[name] = "";
        return;
      }
      const splitArray = numberValue.split(".");
      let strNumber = minusMap[isMinus] + numberValue;
      if (splitArray.length > 1) {
        const fixedStr = precision
          ? splitArray[1].substring(0, precision)
          : splitArray[1];
        if (splitArray.length > precision) {
          strNumber = minusMap[isMinus] + splitArray[0] + "." + fixedStr;
        } else {
          fixedStr &&
            (strNumber = minusMap[isMinus] + splitArray[0] + "." + fixedStr);
        }
      } else {
        strNumber = minusMap[isMinus] + numberValue;
      }
      this.formData.model[name] = strNumber;
    },
    handleNumberChange(name, value) {
      this.formData.model[name] = isNaN(parseFloat(value))
        ? 0
        : parseFloat(value);
    },
    isDateType(type) {
      return this.dateMap.indexOf(type) >= 0;
    },
    //手动上传
    submitUpload(refName, prop) {
      if (prop.onSubmit) {
        prop.onSubmit(prop["file-list"]).then(() => {
          prop["file-list"].forEach(file => {
            file.status = "success";
            file.percentage = 100;
          });
        });
        return false;
      }
      this.$refs[refName][0].submit();
    },
    handleFileError(res) {
      if (/FileExtensionIsNotAllowed/.test(res)) {
        this.$message.error("上传文件格式有误!");
      }
    },
    //合并上传附件的on-progress属性的方法
    mergeProgress(event, file, fileList) {
      this.handleFileProgress(event, file, fileList);
      if (typeof this.progressFun === "function") {
        this.progressFun(event, file, fileList);
      }
    },
    //处理上传附件的进度条解决进度100%时未上传完的问题
    handleFileProgress(event, file, fileList) {
      console.log("处理进度条");
      const allProgress = document.querySelectorAll(".el-progress__text");
      allProgress.forEach(item => {
        if (file.status === "uploading" && parseFloat(item.innerHTML) >= 95) {
          item.innerHTML = "99%";
        }
      });
    },

    // 重置
    resetForm() {
      const props = Object.values(this.formData.props);
      props.forEach(prop => {
        if (prop.type === "file") {
          prop["file-list"] = [];
        }
      });
      this.$refs["xmform"] && this.$refs["xmform"].resetFields();
    },
    // form验证
    validateForm() {
      return new Promise((resolve, reject) => {
        this.$refs["xmform"].validate(valid => {
          valid ? resolve(this.formData.model) : reject();
        });
      });
    },
    //手动获取form表单数据
    getFormData() {
      return this.formData.model;
    },
    //手动修改model里的值，obj是个对象,例如：{text:"1"}
    setModels(obj) {
      for (const [key, value] of Object.entries(obj)) {
        this.$set(this.formData.model, key, value);
      }
    }
  }
};
</script>

<style lang="scss" scoped>
@mixin treeselect-main($size) {
  @if $size== "medium" {
    height: 36px;
    line-height: 20px;
    .vue-treeselect__control {
      height: 36px;
    }
    .vue-treeselect__option {
      padding-top: 4px;
      padding-bottom: 4px;
    }
  } @else if $size== "mini" {
    height: 28px;
    line-height: 15px;
    .vue-treeselect__control {
      height: 28px;
    }
    .vue-treeselect__option {
      padding-top: 3px;
      padding-bottom: 3px;
    }
    .vue-treeselect__placeholder {
      font-size: 12px;
      // padding-bottom: 10px;
    }
  } @else {
    height: 32px;
    line-height: 15px;
    .vue-treeselect__control {
      height: 32px;
    }
    .vue-treeselect__option {
      padding-top: 3px;
      padding-bottom: 3px;
    }
  }
}
.w-100 {
  width: 100%;
}
::v-deep .el-upload__tip {
  margin-top: 0;
}
::v-deep .el-input-group {
  display: flex;
  .el-input-group__append,
  .el-input-group__prepend {
    width: auto;
    display: flex;
    align-items: center;
    > * {
      margin-top: 0;
    }
  }
}
::v-deep .el-upload-list__item .el-progress__text {
  display: none;
}
::v-deep .ql-editor {
  height: 300px; //控制富文本的高度
}
::v-deep .el-radio-group label.el-radio {
  line-height: 2.4 !important;
}
::v-deep .el-range-separator {
  width: auto !important;
}
/* Treeselect */
::v-deep .treeselectMainMini {
  @include treeselect-main("mini");
}
/* Treeselect */
::v-deep .treeselectMainMedium {
  @include treeselect-main("medium");
}
/* Treeselect */
::v-deep .treeselectMainSmall {
  @include treeselect-main("small");
}
</style>
